package org.jeecg.modules.workflow.service.definition;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.fasterxml.jackson.core.type.TypeReference;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.SecurityUtils;
import org.flowable.bpmn.model.BpmnModel;
import org.flowable.bpmn.model.UserTask;
import org.flowable.engine.delegate.DelegateExecution;
import org.jeecg.common.exception.ServiceException;
import org.jeecg.common.system.api.ISysBaseAPI;
import org.jeecg.common.system.vo.LoginUser;
import org.jeecg.common.system.vo.SysRoleModel;
import org.jeecg.common.util.collection.CollectionUtils;
import org.jeecg.common.util.json.JsonUtils;
import org.jeecg.common.workflow.vo.definition.rule.BpmTaskAssignRuleCreateReqVO;
import org.jeecg.common.workflow.vo.definition.rule.BpmTaskAssignRuleRespVO;
import org.jeecg.common.workflow.vo.definition.rule.BpmTaskAssignRuleUpdateReqVO;
import org.jeecg.modules.workflow.convert.definition.BpmTaskAssignRuleConvert;
import org.jeecg.modules.workflow.entity.BpmTaskAssignRule;
import org.jeecg.modules.workflow.enums.definition.BpmTaskAssignRuleTypeEnum;
import org.jeecg.modules.workflow.framework.activiti.core.behavior.script.BpmTaskAssignScript;
import org.jeecg.modules.workflow.framework.activiti.core.util.ActivitiUtils;
import org.jeecg.modules.workflow.mapper.BpmTaskAssignRuleMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.*;

import static org.jeecg.common.exception.util.ServiceExceptionUtil.exception;
import static org.jeecg.common.util.json.JsonUtils.toJsonString;
import static org.jeecg.modules.workflow.enums.ErrorCodeConstants.*;

/**
 * Bpm 任务规则Service业务层处理
 *
 * @author whnk
 * @date 2022-04-20
 */
@Service
@Slf4j
public class BpmTaskAssignRuleServiceImpl implements BpmTaskAssignRuleService
{

    @Resource
    private BpmTaskAssignRuleMapper taskRuleMapper;

    @Resource
    @Lazy // 解决循环依赖
    private BpmModelService modelService;
    @Resource
    @Lazy // 解决循环依赖
    private BpmProcessDefinitionService processDefinitionService;

    @Autowired
    private ISysBaseAPI sysBaseApi;

    @Autowired
    BpmTaskAssignRuleConvert bpmTaskAssignRuleConvert;

    private static final TypeReference<List<String>> listTypeReference = new TypeReference<List<String>>(){};

    private static final TypeReference<List<Long>> scriptTypeReference = new TypeReference<List<Long>>(){};



    /**
     * 任务分配脚本
     */
    private Map<Long, BpmTaskAssignScript> scriptMap = Collections.emptyMap();

    @Resource
    public void setScripts(List<BpmTaskAssignScript> scripts) {
        this.scriptMap = CollectionUtils.convertMap(scripts, script -> script.getEnum().getId());
    }

    @Override
    public List<BpmTaskAssignRule> getTaskAssignRuleListByProcessDefinitionId(String processDefinitionId,
                                                                                String taskDefinitionKey) {
        return taskRuleMapper.selectListByProcessDefinitionId(processDefinitionId,taskDefinitionKey);
    }

    @Override
    public List<BpmTaskAssignRule> getTaskAssignRuleListByModelId(String modelId) {
        return taskRuleMapper.selectListByModelId(modelId);
    }

    @Override
    public List<BpmTaskAssignRuleRespVO> getTaskAssignRuleList(String modelId, String processDefinitionId) {
        // 获得规则
        List<BpmTaskAssignRule> rules = Collections.emptyList();
        BpmnModel model = null;
        if (StrUtil.isNotEmpty(modelId)) {
            rules = getTaskAssignRuleListByModelId(modelId);
            model = modelService.getBpmnModel(modelId);
        } else if (StrUtil.isNotEmpty(processDefinitionId)) {
            rules = getTaskAssignRuleListByProcessDefinitionId(processDefinitionId, null);
            model = processDefinitionService.getBpmnModel(processDefinitionId);
        }
        if (model == null) {
            return Collections.emptyList();
        }

        // 获得用户任务，只有用户任务才可以设置分配规则
        List<UserTask> userTasks = ActivitiUtils.getBpmnModelElements(model, UserTask.class);
        if (CollUtil.isEmpty(userTasks)) {
            return Collections.emptyList();
        }

        // 转换数据
        return bpmTaskAssignRuleConvert.convertList(userTasks, rules);
    }

    @Override
    public String createTaskAssignRule(BpmTaskAssignRuleCreateReqVO reqVO) {
        // 校验参数
        validTaskAssignRuleOptions(reqVO.getType(), reqVO.getOptions());
        // 校验是否已经配置
        BpmTaskAssignRule existRule = taskRuleMapper.selectListByModelIdAndTaskDefinitionKey(
                reqVO.getModelId(), reqVO.getTaskDefinitionKey());
        if (existRule != null) {
            throw exception(TASK_ASSIGN_RULE_EXISTS, reqVO.getModelId(), reqVO.getTaskDefinitionKey());
        }

        // 存储
        BpmTaskAssignRule rule = bpmTaskAssignRuleConvert.convert(reqVO);
        //rule.setProcessDefinitionId(""); // 只有流程模型，才允许新建
        rule.setCreateBy(getUserName());
        rule.setCreateTime(new Date());
        taskRuleMapper.insert(rule);
        return rule.getId();
    }

    @Override
    public void updateTaskAssignRule(BpmTaskAssignRuleUpdateReqVO reqVO) {
        // 校验参数
        validTaskAssignRuleOptions(reqVO.getType(), reqVO.getOptions());
        // 校验是否存在
        BpmTaskAssignRule existRule = taskRuleMapper.selectById(reqVO.getId());
        if (existRule == null) {
            throw exception(TASK_ASSIGN_RULE_NOT_EXISTS);
        }
        // 只允许修改流程模型的规则
        if (StringUtils.isNotEmpty(existRule.getProcessDefinitionId())) {
            throw exception(TASK_UPDATE_FAIL_NOT_MODEL);
        }

        // 执行更新
        BpmTaskAssignRule bpmTaskAssignRule = bpmTaskAssignRuleConvert.convert(reqVO);
        bpmTaskAssignRule.setUpdateBy(getUserName());
        bpmTaskAssignRule.setUpdateTime(new Date());
        taskRuleMapper.updateById(bpmTaskAssignRule);
    }

    @Override
    public boolean isTaskAssignRulesEquals(String modelId, String processDefinitionId) {
        // 调用 VO 接口的原因是，过滤掉流程模型不需要的规则，保持和 copyTaskAssignRules 方法的一致性
        List<BpmTaskAssignRuleRespVO> modelRules = getTaskAssignRuleList(modelId, null);
        List<BpmTaskAssignRuleRespVO> processInstanceRules = getTaskAssignRuleList(null, processDefinitionId);
        if (modelRules.size() != processInstanceRules.size()) {
            return false;
        }

        // 遍历，匹配对应的规则
        Map<String, BpmTaskAssignRuleRespVO> processInstanceRuleMap = CollectionUtils.convertMap(processInstanceRules,
                BpmTaskAssignRuleRespVO::getTaskDefinitionKey);
        for (BpmTaskAssignRuleRespVO modelRule : modelRules) {
            BpmTaskAssignRuleRespVO processInstanceRule = processInstanceRuleMap.get(modelRule.getTaskDefinitionKey());
            if (processInstanceRule == null) {
                return false;
            }
            if (!ObjectUtil.equal(modelRule.getType(), processInstanceRule.getType())
                    || !ObjectUtil.equal(modelRule.getOptions(), processInstanceRule.getOptions())) {
                return false;
            }
        }
        return true;
    }

    @Override
    public void copyTaskAssignRules(String fromModelId, String toProcessDefinitionId) {
        List<BpmTaskAssignRuleRespVO> rules = getTaskAssignRuleList(fromModelId, null);
        if (CollUtil.isEmpty(rules)) {
            return;
        }
        // 开始复制
        List<BpmTaskAssignRule> newRules = bpmTaskAssignRuleConvert.convertList2(rules);
        newRules.forEach(rule -> {
            rule.setProcessDefinitionId(toProcessDefinitionId);
            rule.setId(null);
            rule.setCreateTime(null);
            rule.setUpdateTime(null);
            rule.setCreateBy(getUserName());
            rule.setCreateTime(new Date());
        });
        for(BpmTaskAssignRule bpmTaskAssignRule : newRules){
            taskRuleMapper.insert(bpmTaskAssignRule);
        }
    }

    private void validTaskAssignRuleOptions(Integer type, Set<String> options) {
        List<String> longList = new ArrayList<>();
        longList.addAll(options);
        if (Objects.equals(type, BpmTaskAssignRuleTypeEnum.ROLE.getType())) {
            validRoles(longList);
        } /*else if (ObjectUtils.equalsAny(type, BpmTaskAssignRuleTypeEnum.DEPT_MEMBER.getType(),
                BpmTaskAssignRuleTypeEnum.DEPT_LEADER.getType())) {
            deptApi.validDepts(options);
        } else if (Objects.equals(type, BpmTaskAssignRuleTypeEnum.POST.getType())) {
            postApi.validPosts(options);
        }*/ else if (Objects.equals(type, BpmTaskAssignRuleTypeEnum.USER.getType())) {
            validUsers(longList);
        } /*else if (Objects.equals(type, BpmTaskAssignRuleTypeEnum.USER_GROUP.getType())) {
            userGroupService.validUserGroups(options);
        }*/
        else if (Objects.equals(type, BpmTaskAssignRuleTypeEnum.SCRIPT.getType())) {

        }else {
            throw new IllegalArgumentException(StrUtil.format("未知的规则类型({})", type));
        }
    }

    private String getUserName()
    {
        LoginUser sysUser = (LoginUser) SecurityUtils.getSubject().getPrincipal();
        return sysUser.getRealname();
    }

    public void validRoles(List<String> ids) {
        if (CollUtil.isEmpty(ids)) {
            return;
        }
        // 获得角色信息
        List<SysRoleModel> roles = sysBaseApi.queryRolesByRoleIds(ids.toArray(new String[ids.size()]));
        Map<String, SysRoleModel> roleMap = CollectionUtils.convertMap(roles, SysRoleModel::getId);
        // 校验
        ids.forEach(id -> {
            SysRoleModel role = roleMap.get(id);
            if (role == null) {
                throw exception(ROLE_NOT_EXISTS);
            }
        });
    }

    public void validScripts(){

    }

    public void validUsers(List<String> ids) {
        if (CollUtil.isEmpty(ids)) {
            return;
        }
        // 获得岗位信息
        List<LoginUser> users = sysBaseApi.queryAllUserByIds(ids.toArray(new String[ids.size()]));
        Map<String, LoginUser> userMap = CollectionUtils.convertMap(users, LoginUser::getId);
        // 校验
        ids.forEach(id -> {
            LoginUser user = userMap.get(id);
            if (user == null) {
                throw exception(USER_NOT_EXISTS);
            }
            if (!"1".equals(String.valueOf(user.getStatus()))) {
                throw exception(USER_IS_DISABLE, user.getRealname());
            }
        });
    }

    @Override
    public List<String> calculateTaskCandidateUsers(DelegateExecution execution) {
        BpmTaskAssignRule rule = getTaskRule(execution);
        return calculateTaskCandidateUsers(execution, rule);
    }

    BpmTaskAssignRule getTaskRule(DelegateExecution execution) {
        List<BpmTaskAssignRule> taskRules = getTaskAssignRuleListByProcessDefinitionId(
                execution.getProcessDefinitionId(), execution.getCurrentActivityId());
        if (CollUtil.isEmpty(taskRules)) {
            throw new ServiceException(StrUtil.format("流程任务({}/{}/{}) 找不到符合的任务规则",
                    execution.getId(), execution.getProcessDefinitionId(), execution.getCurrentActivityId()));
        }
        if (taskRules.size() > 1) {
            throw new ServiceException(StrUtil.format("流程任务({}/{}/{}) 找到过多任务规则({})",
                    execution.getId(), execution.getProcessDefinitionId(), execution.getCurrentActivityId()));
        }
        return taskRules.get(0);
    }

    List<String> calculateTaskCandidateUsers(DelegateExecution execution, BpmTaskAssignRule rule) {
        List<String> assigneeUserIds = null;
        if (Objects.equals(BpmTaskAssignRuleTypeEnum.ROLE.getType(), rule.getType())) {
            assigneeUserIds = calculateTaskCandidateUsersByRole(rule);
        } else if (Objects.equals(BpmTaskAssignRuleTypeEnum.USER.getType(), rule.getType())) {
            assigneeUserIds = calculateTaskCandidateUsersByUser(rule);
        }  else if (Objects.equals(BpmTaskAssignRuleTypeEnum.SCRIPT.getType(), rule.getType())) {
            assigneeUserIds = calculateTaskCandidateUsersByScript(execution, rule);
        }

        // 移除被禁用的用户
        removeDisableUsers(assigneeUserIds);
        // 如果候选人为空，抛出异常
        if (CollUtil.isEmpty(assigneeUserIds)) {
            log.error("[calculateTaskCandidateUsers][流程任务({}/{}/{}) 任务规则({}) 找不到候选人]", execution.getId(),
                    execution.getProcessDefinitionId(), execution.getCurrentActivityId(), toJsonString(rule));
            throw exception(TASK_CREATE_FAIL_NO_CANDIDATE_USER);
        }
        return assigneeUserIds;
    }

    private List<String> calculateTaskCandidateUsersByRole(BpmTaskAssignRule rule) {
        List<String> roleIds = JsonUtils.parseObject(rule.getOptions(), listTypeReference);
        List<String> userList = sysBaseApi.queryUserIdsByRoleIds(roleIds.toArray(new String[roleIds.size()]));
        return userList;
    }

    private List<String> calculateTaskCandidateUsersByUser(BpmTaskAssignRule rule) {
        return JsonUtils.parseObject(rule.getOptions(), listTypeReference);
    }

    void removeDisableUsers(List<String> assigneeUserIds) {
        if (CollUtil.isEmpty(assigneeUserIds)) {
            return;
        }
        List<LoginUser> loginUsers = sysBaseApi.queryAllUserByIds(assigneeUserIds.toArray(new String[assigneeUserIds.size()]));
        Map<String, LoginUser> userMap = CollectionUtils.convertMap(loginUsers, LoginUser::getId);
        assigneeUserIds.removeIf(id -> {
            LoginUser user = userMap.get(id);
            return user == null || !"1".equals(String.valueOf(user.getStatus()));
        });
    }

    private List<String> calculateTaskCandidateUsersByScript(DelegateExecution execution, BpmTaskAssignRule rule) {
        // 获得对应的脚本
        List<Long> options = JsonUtils.parseObject(rule.getOptions(), scriptTypeReference);
        List<BpmTaskAssignScript> scripts = new ArrayList<>(options.size());
        options.forEach(id -> {
            BpmTaskAssignScript script = scriptMap.get(id);
            if (script == null) {
                throw exception(TASK_ASSIGN_SCRIPT_NOT_EXISTS, id);
            }
            scripts.add(script);
        });
        // 逐个计算任务
        List<String> userIds = new ArrayList<>();
        scripts.forEach(script -> CollUtil.addAll(userIds, script.calculateTaskCandidateUsers(execution)));
        return userIds;
    }
}
